// $Id: array.hpp 2 2009-09-02 08:47:42Z joseph2002 $
/*
 * Copyright 2009 Joseph Wu
 * Distributed under the Boost Software License, Version 1.0.
 * (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
 *
 * For more information, see http://xpcomplus.sourceforge.net
 */

#ifndef XPCOM_PROXY_ARRAY_HPP
#define XPCOM_PROXY_ARRAY_HPP

#include <xpcom/config.hpp>
#include <algorithm>
#include <xpcom/detail/array_access.hpp>

#ifdef BOOST_HAS_ABI_HEADERS
#include BOOST_ABI_PREFIX
#endif

namespace xpcom {
namespace proxy {

using xpcom::detail::array_access;
using xpcom::detail::array_access_impl;

//
// XPCOM proxy argument translators.
//
// array_in(const array&) --> element_type*
// array_in_size(const array&) --> size_type
// array_in_length(const array&) --> size_type
// array_in_size2(const array&) --> size_type // for same [size_is] and [length_is]
// array_out(array&) --> element_type**
// array_out_size(array&) --> size_type*
// array_out_length(array&) --> size_type*
// array_out_size2(array&) --> size_type*
// array_inout(array&) --> element_type**
// array_inout_size(array&) --> size_type*
// array_inout_length(array&) --> size_type*
// array_inout_size2(array&) --> size_type*
//
//
// Fix for some interface like nsIBinaryInputStream which uses input [size_is]
// param for output array, which is supported through array_out_isize2, call
// it on array parameter.
//
// Fix for some interface like nsIXPInstallManager, nsIFormSigningDialog,
// nsIClientAuthDialogs which has two arrays references one [size_is] param.
// Make array_in_size2 accept up to three arguments.
//
// FIXME other similar condition is not handled, report it you have weird
// interface like this, ...
//

template <typename Array>
XPCOM_FORCEINLINE typename Array::element_type*
array_in(const Array& a)
{
    return array_access_impl(a).data();
}

template <typename Array>
XPCOM_FORCEINLINE typename Array::size_type
array_in_size(const Array& a)
{
    return array_access_impl(a).size();
}

template <typename Array>
XPCOM_FORCEINLINE typename Array::size_type
array_in_length(const Array& a)
{
    return array_access_impl(a).length();
}

template <typename Array>
XPCOM_FORCEINLINE typename Array::size_type
array_in_size2(const Array& a)
{
    return array_access_impl(a).length();
}

template <typename Array1, typename Array2>
inline typename Array1::size_type
array_in_size2(const Array1& a1, const Array2& a2)
{
    return (std::min)(
            array_access_impl(a1).length(), array_access_impl(a2).length());
}

template <typename Array1, typename Array2, typename Array3>
inline typename Array1::size_type
array_in_size2(const Array1& a1, const Array2& a2, const Array3& a3)
{
    return (std::min)(array_in_size2(a1, a2), array_access_impl(a3).length());
}

template <typename Array>
XPCOM_FORCEINLINE typename Array::element_type**
array_out(Array& a)
{
    array_access_impl(a).reset();
    return array_access_impl(a).data_addr();
}

template <typename Array>
XPCOM_FORCEINLINE typename Array::element_type**
array_inout(Array& a)
{
    return array_access_impl(a).data_addr();
}

#ifdef XPCOM_ENABLE_LENGTH_IS

namespace detail {

template <typename Array>
class array_out_size2_aux
{
    typedef typename array_access<Array>::impl_type impl_type;
    typedef typename Array::size_type size_type;
public:
    explicit array_out_size2_aux(Array& a) :
        impl_(array_access_impl(a))
    {
    }

    ~array_out_size2_aux()
    {
        // XXX It is OK to copy several times caused by temporary object
        impl_.size_ = impl_.length_;
    }

    XPCOM_FORCEINLINE
    operator size_type*()
    {
        return &impl_.length_;
    }

private:
    impl_type& impl_;

    const array_out_size2_aux& operator=(const array_out_size2_aux&);
};

template <typename Array>
class array_inout_size2_aux
{
    typedef typename array_access<Array>::impl_type impl_type;
    typedef typename Array::size_type size_type;
public:
    explicit array_inout_size2_aux(Array& a) :
        impl_(array_access_impl(a))
    {
    }

    ~array_inout_size2_aux()
    {
        impl_.size_ = impl_.length_;
    }

    XPCOM_FORCEINLINE
    operator size_type*()
    {
        return &impl_.length_;
    }

private:
    impl_type& impl_;

    const array_inout_size2_aux& operator=(const array_inout_size2_aux&);
};

template <typename Array>
class array_out_isize2_aux
{
    typedef typename array_access<Array>::impl_type impl_type;
    typedef typename Array::element_type element_type;
    typedef typename Array::size_type size_type;
public:
    array_out_isize2_aux(Array& a, size_type size) :
        impl_(array_access_impl(a)),
        size_(size)
    {
        impl_.reset(); // XXX reset several times is OK
    }

    ~array_out_isize2_aux()
    {
        if (!impl_.data_)
            impl_.size_ = impl_.length_ = 0;
        else
            impl_.size_ = impl_.length_ = size_;
    }

    XPCOM_FORCEINLINE
    operator element_type**()
    {
        return impl_.data_addr();
    }

private:
    impl_type& impl_;
    size_type size_;

    const array_out_isize2_aux& operator=(const array_out_isize2_aux&);
};

} // namespace xpcom::proxy::detail

template <typename Array>
XPCOM_FORCEINLINE typename Array::size_type*
array_out_size(Array& a)
{
    return &array_access_impl(a).size_;
}

template <typename Array>
XPCOM_FORCEINLINE typename Array::size_type*
array_out_length(Array& a)
{
    return &array_access_impl(a).length_;
}

template <typename Array>
XPCOM_FORCEINLINE detail::array_out_size2_aux<Array>
array_out_size2(Array& a)
{
    return detail::array_out_size2_aux<Array>(a);
}

template <typename Array>
XPCOM_FORCEINLINE typename Array::size_type*
array_inout_size(Array& a)
{
    return &array_access_impl(a).size_;
}

template <typename Array>
XPCOM_FORCEINLINE typename Array::size_type*
array_inout_length(Array& a)
{
    return &array_access_impl(a).length_;
}

template <typename Array>
XPCOM_FORCEINLINE detail::array_inout_size2_aux<Array>
array_inout_size2(Array& a)
{
    return detail::array_inout_size2_aux<Array>(a);
}

template <typename Array>
XPCOM_FORCEINLINE detail::array_out_isize2_aux<Array>
array_out_isize2(Array& a, typename Array::size_type size)
{
    return detail::array_out_isize2_aux<Array>(a, size);
}

#else // XPCOM_ENABLE_LENGTH_IS

namespace detail {

template <typename Array>
class array_out_size_aux
{
    typedef typename Array::size_type size_type;
public:
    explicit array_out_size_aux(Array&) :
        size_(0)
    {
    }

    XPCOM_FORCEINLINE
    operator size_type*()
    {
        return &size_;
    }

private:
    size_type size_; // ignored

    const array_out_size_aux& operator=(const array_out_size_aux&);
};

template <typename Array>
class array_inout_size_aux
{
    typedef typename Array::size_type size_type;
public:
    explicit array_inout_size_aux(Array& a) :
        size_(a.capacity())
    {
    }

    XPCOM_FORCEINLINE
    operator size_type*()
    {
        return &size_;
    }

private:
    size_type size_; // ignored

    const array_inout_size_aux& operator=(const array_inout_size_aux&);
};

template <typename Array>
class array_out_isize2_aux
{
    typedef typename array_access<Array>::impl_type impl_type;
    typedef typename Array::element_type element_type;
    typedef typename Array::size_type size_type;
public:
    array_out_isize2_aux(Array& a, size_type size) :
        impl_(array_access_impl(a)),
        size_(size)
    {
        impl_.reset(); // XXX reset several times is OK
    }

    ~array_out_isize2_aux()
    {
        impl_.size_ = impl_.data_ ? size_ : 0;
    }

    XPCOM_FORCEINLINE
    operator element_type**()
    {
        return impl_.data_addr();
    }

private:
    impl_type& impl_;
    size_type size_;

    const array_out_isize2_aux& operator=(const array_out_isize2_aux&);
};

} // namespace xpcom::proxy::detail

template <typename Array>
XPCOM_FORCEINLINE detail::array_out_size_aux<Array>
array_out_size(Array& a)
{
    return detail::array_out_size_aux<Array>(a);
}

template <typename Array>
XPCOM_FORCEINLINE typename Array::size_type*
array_out_length(Array& a)
{
    return &array_access_impl(a).size_;
}

template <typename Array>
XPCOM_FORCEINLINE typename Array::size_type*
array_out_size2(Array& a)
{
    return &array_access_impl(a).size_;
}

template <typename Array>
XPCOM_FORCEINLINE detail::array_inout_size_aux<Array>
array_inout_size(Array& a)
{
    return detail::array_inout_size_aux<Array>(a);
}

template <typename Array>
XPCOM_FORCEINLINE typename Array::size_type*
array_inout_length(Array& a)
{
    return &array_access_impl(a).size_;
}

template <typename Array>
XPCOM_FORCEINLINE typename Array::size_type*
array_inout_size2(Array& a)
{
    return &array_access_impl(a).size_;
}

template <typename Array>
XPCOM_FORCEINLINE detail::array_out_isize2_aux<Array>
array_out_isize2(Array& a, typename Array::size_type size)
{
    return detail::array_out_isize2_aux<Array>(a, size);
}

#endif // XPCOM_ENABLE_LENGTH_IS

} // namespace xpcom::proxy
} // namespace xpcom

#ifdef BOOST_HAS_ABI_HEADERS
#include BOOST_ABI_SUFFIX
#endif

#endif // XPCOM_PROXY_ARRAY_HPP
